// Package goext 读取ext2文件系统。
package goext

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"unsafe"
)

const (
	// OSLinux linux.
	OSLinux = iota
	// OSHurd the GNU OS.
	OSHurd
	// OSMasix UNKNOWN
	OSMasix
	// OSFreeBSD freeBSD
	OSFreeBSD
	// OSLites lites OS
	OSLites
)

const (
	nonDirBlocks  = 12
	indirectBlock = nonDirBlocks
	dIndBlock     = indirectBlock + 1
	tIndBlocks    = dIndBlock + 1
	nBlocks       = tIndBlocks + 1

	rootInode     = 2
	fsBlockOffset = 1
	MinBlockSize  = 1024

	ModeInodeFmt     = 0o0170000
	ModeInodeSock    = 0o0140000
	ModeInodeFileReg = 0o100000
	ModeInodeFileDir = 0o040000
	/*
		#define S_IFLNK	 0120000
		#define S_IFBLK  0060000
	*/
/*
#define S_IFCHR  0020000
#define S_IFIFO  0010000
#define S_ISUID  0004000
#define S_ISGID  0002000
#define S_ISVTX  000100
*/
)

var (
	osMap map[int]string = map[int]string{
		OSLinux:   "linux",
		OSHurd:    "hurd",
		OSMasix:   "masix",
		OSFreeBSD: "freeBSD",
		OSLites:   "lites",
	}
	blockSize int = 1024
)

// OSLiteral 将整型转为可读的字符串。
func OSLiteral(os int) string {
	v, exists := osMap[os]
	if exists {
		return v
	}
	return "unknown"
}

// SuperBlock Ext2的超级块。
type SuperBlock struct {
	InodesCount        uint32
	BlocksCount        uint32
	ReserveBlocksCount uint32
	FreeBlocksCount    uint32
	FreeInodesCount    uint32
	FirstDataBlock     uint32

	LogBlockSize uint32
	LogFragSize  uint32

	BlocksPerGroup uint32
	FragsPerGroup  uint32
	InodesPerGroup uint32

	MountTime uint32
	WriteTime uint32

	MountCount    uint16
	MaxMountCount uint16

	Magic  uint16
	State  uint16
	Errors uint16

	MinorRevisionLevel uint16

	LastCheckTime uint32
	CheckInterval uint32
	CreatorOS     uint32
	RevisionLevel uint32

	DefaultResUID uint16
	DefaultResGID uint16

	FirstInode       uint32
	InodeSize        uint16
	BlockGroupNumber uint16
	FeatureCompat    uint32
	FeatureInCompat  uint32
	FeatureROCompat  uint32

	UUID           [16]uint8
	VolumeName     [16]byte
	LastMountedDir [64]byte
	AlgUsageBitmap uint32

	PreallocBlocks    uint8
	PreallocDirBlocks uint8
	// pad
	_ uint16

	JournalUUID         [16]uint8
	JournalInodeNum     uint32
	JournalDev          uint32
	LastOrphan          uint32
	HashSeed            [4]uint32
	DefaultHashVersion  uint8
	ReservedCharPad     uint8
	ReservedWordPad     uint16
	DefaultMountOpts    uint32
	FirstMetaBlockGroup uint32
	// pad
	_ [190]uint32
}

func (sb SuperBlock) groupsCount() uint {

	div := uint(sb.BlocksCount) / uint(sb.BlocksPerGroup)
	reminder := uint(sb.BlocksCount) % uint(sb.BlocksPerGroup)
	if reminder > 0 {
		div++
	}

	return div
}

// Inode 磁盘上的inode。
type Inode struct {
	Mode uint16
	UID  uint16
	Size uint32

	AccessTime       uint32
	CreateTime       uint32
	ModificationTime uint32
	DeletionTime     uint32

	GID        uint16
	LinksCount uint16
	Blocks     uint32
	Flags      uint32
	_          uint32

	Block      [nBlocks]uint32
	Generation uint32
	FileACL    uint32
	DirACL     uint32
	FragAddr   uint32

	_ [3]uint32
}

func (i *Inode) Dump(ext2 *Ext2) {
	fmt.Println("[i] start do dump")
	defer fmt.Println("[i] done to start do dump")
	fmt.Printf("Uid: %d\n", i.UID)
	fmt.Printf("Size : %d\n", i.Size)
	fmt.Printf("Mode: %o\n", i.Mode)
	fmt.Printf("Blocks: %d\n", i.Blocks)
	if i.IsDir() {
		fmt.Println("Is dir")
		for idx, b := range i.Block {
			if uint32(idx) >= i.Blocks {
				return
			}
			offset := int(b) * blockSize
			buf, size, err := ext2.dr.Read(int64(offset), blockSize)
			if err != nil {
				return
			}
			b := bytes.NewBuffer(buf)
			for size > 0 {
				var e dirEntry
				err = binary.Read(b, binary.LittleEndian, &e)
				if err != nil {
					fmt.Println(err)
					return
				}
				if e.Inode == 0 {
					return
				}
				nameLen := int(e.RecLen) - int(unsafe.Sizeof(dirEntry{}))
				name := make([]byte, nameLen)
				size = size - int(e.RecLen)
				b.Read(name)
				fmt.Printf("name: %s, inode: %d\n", string(name[0:e.NameLen]), e.Inode)
				if e.Inode >= 12 {
					in, err := ext2.findInode(e.Inode)
					if err != nil {
						fmt.Println(err)
					} else {
						fmt.Printf("inode of %d, size: %d\n", e.Inode, in.Size)
					}
				}
			}
		}
	}
	if i.IsReg() {
		fmt.Println("Is reg")
	}
}

func (i *Inode) IsDir() bool {
	return i.Mode&ModeInodeFmt == ModeInodeFileDir
}

func (i *Inode) IsReg() bool {
	return i.Mode&ModeInodeFmt == ModeInodeFileReg
}

// GroupDesc 组描述符
// super | group desc | block bitmap | inode bitmap | inode table | data blocks
//  1    | n          | 1            | 1            | n           | n
type GroupDesc struct {
	BlockBitmap     uint32
	InodeBitmap     uint32
	InodeTable      uint32
	FreeBlocksCount uint16
	FreeInodesCount uint16
	UsedDirsCount   uint16
	_               uint16
	_               [3]uint32
}

func (gd *GroupDesc) Dump() {
	fmt.Printf("[gd] Start dumping Group Descriptor\n")
	defer fmt.Printf("[gd] Done dumping Group Descriptor\n")

	fmt.Printf("Block bitmap at: %d block\n", gd.BlockBitmap)
	fmt.Printf("Inode bitmap at: %d block\n", gd.InodeBitmap)
	fmt.Printf("Inode table at: %d block\n", gd.InodeTable)
	fmt.Printf("Free blocks: %d\n", gd.FreeBlocksCount)
	fmt.Printf("Free inodes: %d\n", gd.FreeInodesCount)
}

func (gd *GroupDesc) findInode(offset uint32, ino uint32, ext2 *Ext2) (*Inode, error) {
	size := int(ext2.sb.InodesPerGroup) * int(unsafe.Sizeof(Inode{}))
	o := int64(int(gd.InodeTable) * blockSize)
	buf, c, err := ext2.dr.Read(o, size)
	if c != size {
		fmt.Printf("Failed to load inode table,, err: %v\n", err)
		return nil, errors.New("failed to load")
	}

	b := bytes.NewBuffer(buf)
	for i := 0; i < int(ext2.sb.InodesPerGroup); i++ {
		var inode Inode
		if err := binary.Read(b, binary.LittleEndian, &inode); err != nil {
			fmt.Printf("Failed to parse root inode, err: %v\n", err)
			return nil, err
		}
		if uint32(i+1) == offset {
			return &inode, nil
		}
	}
	return nil, errors.New("not found")
}

// DirEntry 目录项
type DirEntry struct {
	Inode   uint32
	RecLen  uint16
	NameLen uint16
	Name    []byte
}

// dirEntry 目录项
type dirEntry struct {
	Inode    uint32
	RecLen   uint16
	NameLen  uint8
	FileType uint8
}

// DirEntry2 目录项
type DirEntry2 struct {
	Inode    uint32
	RecLen   uint16
	NameLen  uint8
	FileType uint8
	Name     []byte
}

// Ext2 filesystem
type Ext2 struct {
	sb     *SuperBlock
	dr     *DiskReader
	groups []GroupDesc
	root   *Inode
}

// FSer file system interface.
type FSer interface {
	Ls(path string) ([]string, error)
}

// Ls list files.
func (e Ext2) Ls(path string) ([]string, error) {
	return nil, nil
}

func (e *Ext2) init() {
	// fmt.Printf("Block group number: %#v\n", e.sb)
	fmt.Printf("Last mount on : %s\n", string(e.sb.LastMountedDir[:]))
	fmt.Printf("First inode : %d\n", e.sb.FirstInode)
	fmt.Printf("Group count: %d\n", e.sb.groupsCount())
	// Load all group
	count := e.sb.groupsCount()*uint(unsafe.Sizeof(GroupDesc{}))/uint(blockSize) + 1
	fmt.Printf("Block of group desc size: %d\n", count)
	var offset int64
	// If blockSize is not default MinBlockSize, then reserved 0-MinBlockSize
	// is the MBR, and MinBlockSize-2*MinBlockSize is the SuperBlock, they both
	// belong to the 0-blockSize. So the Block Group Desc is at 1blockSize.
	if blockSize > MinBlockSize {
		offset = int64(fsBlockOffset * blockSize)
	} else {
		offset = int64((fsBlockOffset + 1) * blockSize)
	}
	buf, c, err := e.dr.Read(offset, int(count*uint(blockSize)))
	if c != blockSize {
		fmt.Printf("Failed to load group desc: %v\n", err)
		return
	}
	e.groups = make([]GroupDesc, e.sb.groupsCount())
	var i uint
	b := bytes.NewBuffer(buf)
	for ; i < e.sb.groupsCount(); i++ {
		var gd GroupDesc
		if err := binary.Read(b, binary.LittleEndian, &gd); err != nil {
			fmt.Printf("Failed to load group desc, err: %v\n", err)
			return
		}
		e.groups[i] = gd
		gd.Dump()
	}
	offset = int64(int(e.groups[0].InodeTable) * blockSize)
	buf, c, err = e.dr.Read(offset, blockSize)
	if c != blockSize {
		fmt.Printf("Failed to load inode table,, err: %v\n", err)
		return
	}

	b = bytes.NewBuffer(buf)
	var inode Inode
	// skip the first inode. Root starts at 2.
	binary.Read(b, binary.LittleEndian, &inode)
	if err := binary.Read(b, binary.LittleEndian, &inode); err != nil {
		fmt.Printf("Failed to parse root inode, err: %v\n", err)
		return
	}

	e.root = &inode
	inode.Dump(e)
}

func (e *Ext2) findInode(ino uint32) (*Inode, error) {
	group := ino / e.sb.InodesPerGroup
	offset := ino % e.sb.InodesPerGroup
	fmt.Printf("Inode of %d in group %d\n", ino, group)
	return e.groups[group].findInode(offset, ino, e)
}

// LoadExt2 从字节流中解析出超级块。
func LoadExt2(dr *DiskReader) (FSer, error) {

	buf, count, err := dr.Read(int64(fsBlockOffset*blockSize), blockSize)
	if err != nil {
		return nil, err
	}
	if count != blockSize {
		return nil, errors.New("not enough read")
	}
	b := bytes.NewBuffer(buf)
	sb := &SuperBlock{}
	if err := binary.Read(b, binary.LittleEndian, sb); err != nil {
		return nil, err
	}

	fmt.Printf("Log2blocksize is %d\n", sb.LogBlockSize)
	blockSize = MinBlockSize << sb.LogBlockSize
	ext2 := &Ext2{dr: dr, sb: sb}
	ext2.init()
	return ext2, nil
}
